﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Collections.ObjectModel;

namespace pHM.DVBLib.Common
{
    public static class Logger
    {
        public static List<LogItem> LogItems { get; set; }
        public static ObservableCollection<LogItem> ObservableLogItems { get; set; }

        public static LogLevels LogLevel = LogLevels.All;

        public static string LogFolder = "pH-Mb\\LungSounds";
        public static string LogFileFormat = "LungSounds-{0:yyyy-MM-dd_hh-mm-ss-tt}.log";

        public static event EventHandler LogUpdated;

        private static FileStream _logStream;
        private static StreamWriter _logWriter;

        private static void onLogUpdate(LogItem item)
        {
            if (LogUpdated != null)
            {
                LogUpdated.Invoke(item, EventArgs.Empty);
            }
        }

        public static void AddAndThrow(string text, string sender, LogLevels logLevel, Type exceptionType, params object[] exceptArgs)
        {
            object[] args = new object[exceptArgs.Length + 1];
            args[0] = text;
            exceptArgs.CopyTo(args, 1);

            Exception innerException = (Exception)Activator.CreateInstance(exceptionType, args);
            AddLogItem(text, sender, logLevel, innerException);
            throw innerException;
        }

        public static void AddLogItem(string text, string sender, LogLevels logLevel, Exception innerException = null)
        {
            if (LogItems == null) LogItems = new List<LogItem>();
            if (ObservableLogItems == null) ObservableLogItems = new ObservableCollection<LogItem>();

            if (_logStream == null)
            {
                string dataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), LogFolder);

                if (!Directory.Exists(dataFolder)) Directory.CreateDirectory(dataFolder);

                _logStream = File.OpenWrite(Path.Combine(dataFolder, string.Format(LogFileFormat, DateTime.Now)));
                _logWriter = new StreamWriter(_logStream);
                _logWriter.AutoFlush = true;
            }

            LogItem item = new LogItem() { Text = text.Replace('\0', '?'), Sender = sender, innerException = innerException, LogLevel = logLevel };
            LogItems.Add(item);
            ObservableLogItems.Add(item);

            if ((LogLevel & logLevel) == logLevel)
            {
                onLogUpdate(item);
                _logWriter.WriteLine(item);
            }
        }

        public static string GetLogText(bool getAll = false)
        {
            StringBuilder logText = new StringBuilder();

            if (LogItems == null) LogItems = new List<LogItem>();

            foreach (LogItem logItem in LogItems)
            {
                if ((LogLevel & logItem.LogLevel) == logItem.LogLevel || getAll)
                {
                    logText.AppendFormat("{0}\r\n", logItem.ToString());
                }
            }

            return logText.ToString();
        }

        public static string DumpObject(object element, int indent = 0, bool recurse = false)
        {
            if (element == null) return "";

            Type objType = element.GetType();
            string indentString = new string(' ', indent);

            StringBuilder output = new StringBuilder();

            if (objType.IsArray)
            {
                foreach (var item in (Array)element)
                {
                    output.AppendFormat("{0}\r\n", DumpObject(item, indent + 2));
                }
            }
            else
            {
                MemberInfo[] members = objType.GetMembers();

                foreach (MemberInfo member in members)
                {
                    switch (member.MemberType)
                    {
                        case MemberTypes.Property:

                            PropertyInfo property = objType.GetProperty(member.Name);
                            try
                            {
                                object propValue = property.GetValue(element, null);
                                if (property.PropertyType.Assembly == objType.Assembly)
                                {
                                    if (recurse)
                                    {
                                        output.AppendFormat("{0}{1}:\r\n{2}", indentString, property.Name, DumpObject(propValue, indent + 2, recurse));

                                    }
                                    else
                                    {
                                        output.AppendFormat("{0}{1}:\r\n", indentString, property.Name);
                                    }
                                }
                                else
                                {
                                    output.AppendFormat("{0}{1}:\t{2}\r\n", indentString, property.Name, propValue);
                                }
                            }
                            catch
                            {
                                output.AppendFormat("{0}{1}:\tException thrown!\r\n", indentString, property.Name);
                            }
                            break;
                        case MemberTypes.Field:
                            FieldInfo field = objType.GetField(member.Name);
                            object fieldValue = field.GetValue(element);

                            if (field.FieldType.Assembly == objType.Assembly)
                            {
                                output.AppendFormat("{0} - {1}:\r\n", indentString, field.Name);
                                //DumpObject(fieldValue, indent + 2);
                            }
                            else
                            {
                                output.AppendFormat("{0} - {1}:\t{2}\r\n", indentString, field.Name, fieldValue);
                            }
                            break;
                    }


                }
            }

            return output.ToString();
        }
    }
}
